#!/bin/bash
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

set -o errexit
set -o pipefail
set -o nounset

# meson_init.sh configures OpenTitan's Meson build system. Rather than calling
# Meson directly, this script should be used instead, since it handles things
# that Meson does not out of the box, such as:
# - The RISC-V toolchain.
# - Idempotence.
# - Building for multiple device platforms.
#
# This script's semantics for creating build directories are as follows:
# - By default, this script will create any missing build directories and
#   configure them. It is idempotent: if there is no work to be done, nothing
#   will change and this script will exit successfully.
# - Passing -r will reconfigure existing build directories. This should be used
#   when editing meson.build files.
# - Passing -f will erase existing build directories. This should be used when
#   existing configuration is completely broken, or a clean build is desired
#   (neither of these should be necessary).
# - Passing -A will explicitly disable idempotence, and cause the script to exit
#   if a build directory already exists. This should be used only in CI, where
#   such error-checking is desireable.
#
# Note that only the first option above is actually guaranteed to be idempotent.

. util/build_consts.sh

echo "Detected \$REPO_TOP at $REPO_TOP."
echo "Object directory set at $OBJ_DIR."
echo "Binary directory set at $BIN_DIR."
echo "OpenTitan version: $OT_VERSION"
echo

function usage() {
  cat << USAGE
Configure Meson build targets.

Usage: $0 [-r|-f|-A|-K|-c] [-T PATH] [-t FILE]

  -A: Assert that no build dirs exist when running this command.
  -c: Enable coverage (requires clang).
  -f: Force a reconfiguration by removing existing build dirs.
  -K: Keep include search paths as generated by Meson.
  -r: Reconfigure build dirs, if they exist.
  -t FILE: Configure Meson with toolchain configuration FILE
  -T PATH: Build tock from PATH rather than the remote repository.

USAGE
}

readonly DEFAULT_RISCV_TOOLS=/tools/riscv
TOOLCHAIN_PATH="${TOOLCHAIN_PATH:-$DEFAULT_RISCV_TOOLS}"

FLAGS_assert=false
FLAGS_force=false
FLAGS_reconfigure=""
FLAGS_keep_includes=false
FLAGS_specified_toolchain_file=false
FLAGS_coverage=false
ARG_toolchain_file="${TOOLCHAIN_PATH}/meson-riscv32-unknown-elf-clang.txt"
ARG_tock_dir=""
# `getopts` usage
# - The initial colon in the optstring is to suppress the default error
#   handling.
# - The remaining options are specified in alphabetical order, and the case
#   statement should match this order.
# - Only options that take an argument should have a following colon.
# - The case statement contains two additional cases:
#   - when `$flag` = `?`, this is an unexpected option.
#   - when `$flag` = `:`, this is the case that a flag which requires an
#     argument is not provided one. In both cases, `$OPTARG` contains the
#     relevant parsed option.
# - After option parsing is finished, we `shift` by `$OPTIND - 1` so that the
#   remaining (unprocessed) arguments are in `$@` (and $1, $2, $3 etc.).
while getopts ':AcfKrt:T:' flag; do
  case "${flag}" in
    A) FLAGS_assert=true;;
    c) FLAGS_coverage=true;;
    f) FLAGS_force=true;;
    K) FLAGS_keep_includes=true;;
    r) FLAGS_reconfigure="--reconfigure";;
    t) FLAGS_specified_toolchain_file=true
       ARG_toolchain_file="${OPTARG}";;
    T) ARG_tock_dir="${OPTARG}";;
    \?) echo "Unexpected option: -${OPTARG}" >&2
        usage
        exit 1
        ;;
    :) echo "Option -${OPTARG} requires a path argument" >&2
       usage
       exit 1
       ;;
    *) echo "Internal Error: Unhandled option: -${flag}" >&2
       exit 1
       ;;
  esac
done
shift $((OPTIND - 1))

# We do not accept additional arguments.
if [[ "$#" -gt 0 ]]; then
  echo "Unexpected arguments:" "$@" >&2
  exit 1
fi

if [[ ! -n "$(command -v meson)" ]]; then
  echo "Unable to find meson. Please install meson before running this command." >&2
  exit 1
fi

if [[ ! -n "$(command -v ninja)" ]]; then
  echo "Unable to find ninja. Please install ninja before running this command." >&2
  exit 1
fi

if [[ "${FLAGS_force}" == true ]]; then
  rm -rf "$OBJ_DIR"
  rm -rf "$BIN_DIR"
fi

if [[ "${FLAGS_assert}" == true ]]; then
  if [[ -e "$OBJ_DIR" ]]; then
    echo "Object directory at $OBJ_DIR already exists. Aborting." >&2
    exit 1
  fi
  if [[ -e "$BIN_DIR" ]]; then
    echo "Binary directory at $BIN_DIR already exists. Aborting." >&2
    exit 1
  fi
fi

TOCK_LOCAL=false
if [[ ! -z "${ARG_tock_dir}" ]]; then
  TOCK_LOCAL=true

  if [[ ! -d "${ARG_tock_dir}" ]]; then
    echo "Referenced Tock directory ${ARG_tock_dir} doesn't exist. Aborting." >&2
    exit 1
  fi

  if [[ ! -f "${TOCK_SYMLINK}" || -L "${TOCK_SYMLINK}" ]]; then
    echo "Creating symlink to Tock project at ${ARG_tock_dir}."
    ln -sf "${ARG_tock_dir}" "${TOCK_SYMLINK}"
  elif [[ -d "${TOCK_SYMLINK}" ]]; then
    echo "Tock directory $TOCK_SYMLINK exists in place. Leaving as is."
  else
    echo "Placing Tock at ${TOCK_SYMLINK} would clobber something. Aborting." >&2
    exit 1
  fi
fi

reconf="${FLAGS_reconfigure}"

if [[ ! -d "$OBJ_DIR" ]]; then
  echo "Output directory does not exist at $OBJ_DIR; creating." >&2
  mkdir -p "$OBJ_DIR"
  reconf=""
elif [[ -z "$reconf" ]]; then
  echo "Output directory already exists at $OBJ_DIR; skipping." >&2
fi

if [[ -f "${ARG_toolchain_file}" ]]; then
  echo "Using meson toolchain file at $ARG_toolchain_file." >&2
else
  if [[ "${FLAGS_specified_toolchain_file}" == true ]]; then
    echo "Unable to find meson toolchain file at $ARG_toolchain_file. Aborting." >&2
    exit 1
  else
    cross_file="$OBJ_DIR/toolchain-configured.txt"
    cp toolchain.txt "$cross_file"
    perl -pi -e "s#$DEFAULT_RISCV_TOOLS#$TOOLCHAIN_PATH#g" "$cross_file"
    touch -r toolchain.txt "$cross_file"
    echo "Set up toolchain file at $cross_file." >&2
    ARG_toolchain_file="${cross_file}"
  fi
fi

mkdir -p "$BIN_DIR"
set -x
meson $reconf \
  -Dot_version="$OT_VERSION" \
  -Dbin_dir="$BIN_DIR" \
  -Dtock_local="$TOCK_LOCAL" \
  -Dkeep_includes="$FLAGS_keep_includes" \
  -Dcoverage="$FLAGS_coverage" \
  --cross-file="$ARG_toolchain_file" \
  "$OBJ_DIR"
